﻿using System;
using System.Collections.Generic;

namespace Smarly.Bussines
{
	public class SmarlyQueue<T>
	{
		private class Element
		{
			internal int Index { get; set; }
			internal T Value { get; set; }
			internal string Hash { get; set; }

			internal DateTime ExpireDate { get; set; }

			internal int RequestCount { get; set; }

			internal bool IsExpired
			{
				get { return ExpireDate < DateTime.UtcNow; }
			}

		}

		public SmarlyQueue(int size)
		{
			_array = new Element[size > 0 ? size : 1];
		}

		private readonly Element[] _array;
		private readonly Dictionary<string, Element> _hashes = new Dictionary<string, Element>();

		private readonly object _lock = new object();

		private const int ExpMinutes = 60;

		public void Remove(string hash)
		{
			lock (_lock)
			{
				if (!_hashes.ContainsKey(hash))
					return;

				_hashes[hash].ExpireDate = new DateTime();
			}
		}

		public void Clear()
		{
			lock (_lock)
			{
				_hashes.Clear();
				Array.Clear(_array, 0, _array.Length);
			}
		}

		public T AddOrReplace(string hash, T element, DateTime? expiration = null)
		{
			lock (_lock)
			{
				RemoveExpired();
				bool newElement = false;

				Element obj;

				var expireDate = expiration.HasValue ? expiration.Value.ToUniversalTime() : DateTime.UtcNow.AddMinutes(ExpMinutes);

				if (_hashes.ContainsKey(hash))
				{
					obj = _hashes[hash];
					obj.Value = element;
					obj.ExpireDate = expireDate;
				}
				else
				{
					obj = new Element
						      {
							      Value = element,
							      Index = _array.Length - 1,
							      Hash = hash,
							      ExpireDate = expireDate,
						      };
					_hashes.Add(hash, obj);
					newElement = true;
				}
				MoveToFirst(obj, newElement);

			}


			return element;
		}


		private void MoveToFirst(Element obj, bool newElement)
		{
			int currentIndex = obj.Index;

			if (newElement && currentIndex + 1 == _array.Length)
			{
				var deleted = _array[currentIndex];
				if (deleted != null)
					_hashes.Remove(deleted.Hash);
			}

			for (int i = currentIndex; i > 0; i--)
			{
				var prev = _array[i - 1];
				if (prev != null)
					prev.Index = i;
				_array[i] = prev;
			}

			obj.Index = 0;
			_array[0] = obj;

			obj.RequestCount++;
		}

		public bool TryGet(string hash, ref T element)
		{
			lock (_lock)
			{
				Element obj;

				if (!_hashes.ContainsKey(hash))
					return false;

				obj = _hashes[hash];

				if (obj.IsExpired)
					return false;

				element = obj.Value;
				MoveToFirst(obj, false);

				return true;
			}
		}


		private void RemoveExpired()
		{
			int position = _array.Length;
			bool freePosition = false;
			for (int i = 0; i < _array.Length; i++)
			{
				var current = _array[i];
				if (current == null || current.IsExpired)
				{
					if (!freePosition)
					{
						freePosition = true;
						position = i;

					}

					if (current != null)
					{
						_hashes.Remove(current.Hash);
						_array[i] = null;
					}
				}
				else
				{
					if (freePosition)
					{
						_array[position++] = _array[i];
						_array[i] = null;
					}
				}
			}
		}
	}
}